/**
 * 代码生成工具
 */
import tnx from '@/tnx.js';
import BaseType from '../entity/BaseType.js';
import EnumClass from '../entity/EnumClass.js';
import EntityClass from '../entity/EntityClass.js';
import ValueClass from '../entity/ValueClass.js';
import PlainClass from '../entity/PlainClass.js';
import EntityPropertyMapping from '../entity/EntityPropertyMapping.js';
import TypeKind from '../enums/TypeKind.js';
import PhysicalIndexType from '../enums/PhysicalIndexType.js';
import InheritanceStrategy from '../enums/InheritanceStrategy.js';
import PhysicalTypeNecessity from '../enums/PhysicalTypeNecessity.js';
import CodingClassKind from '../../coding/model/CodingClassKind.js';
import CodingClass from '../../coding/model/CodingClass.js';
import CodingType from '../../coding/model/CodingType.js';
import CodingProperty from '../../coding/model/CodingProperty.js';
import CodingMethod from '../../coding/model/CodingMethod.js';
import CodingMethodParameter from '../../coding/model/CodingMethodParameter.js';
import {LIST_CLASS_NAME, STRING_CLASS_NAME} from './dependency-lib-jdk.js';
import PersistUtil from './persist.js';

const DEFAULT_ENTITY_TAG_NAME = 'entity';
const KEYWORD_NAME = 'keyword';

export default {
    entityCodeTypeItems: [{
        name: 'entity',
        caption: '实体类',
    }, {
        name: 'sql',
        caption: '建表SQL',
        language: 'sql',
    }, {
        name: 'jpa',
        caption: 'JPA映射',
        language: 'xml',
        subs: [{
            name: 'converter',
            caption: '枚举类型转换器',
        }],
    }, {
        name: 'querying',
        caption: '查询模型',
    }, {
        name: 'repo',
        caption: 'Repo',
        subs: [{
            name: 'repox',
            caption: 'Repox',
        }, {
            name: 'repoImpl',
            caption: 'RepoImpl',
        }],
    }, {
        name: 'command',
        caption: '命令模型',
    }, {
        name: 'service',
        caption: 'Service',
        subs: [{
            name: 'serviceImpl',
            caption: 'ServiceImpl',
        }],
    }, {
        name: 'controller',
        caption: 'Controller',
    }],
    getEntityCodeTypeCaptionMapping() {
        let mapping = {};
        for (let item of this.entityCodeTypeItems) {
            mapping[item.name] = item.caption;
            if (item.subs) {
                for (let sub of item.subs) {
                    mapping[sub.name] = sub.caption;
                }
            }
        }
        return mapping;
    },
    getEntityCodeTypeItem(codeType) {
        if (codeType) {
            for (let item of this.entityCodeTypeItems) {
                if (item.name === codeType) {
                    return item;
                }
                if (item.subs) {
                    for (let sub of item.subs) {
                        if (sub.name === codeType) {
                            return sub;
                        }
                    }
                }
            }
        }
        return null;
    },
    generateGenericTypeCaption(app, genericType, simple = false, refClassConsumer) {
        let caption = genericType.parameter + ' ';
        if (typeof genericType.extended === 'boolean') {
            if (!caption.trim()) {
                caption = '? ';
            }
            if (genericType.extended) {
                caption += 'extends ';
            } else {
                caption += 'super ';
            }
        }
        if (genericType.simpleName) {
            if (simple) {
                caption += genericType.simpleName;
            } else {
                if (genericType.name) {
                    caption += genericType.name;
                } else {
                    if (genericType.packageName) {
                        caption += genericType.packageName + '.';
                    }
                    caption += genericType.simpleName;
                }
            }
        } else if (genericType.typeId) {
            let refClass = app.findClass(genericType.typeId, true, true);
            if (refClass) {
                if (simple) {
                    caption += refClass.simpleName;
                } else {
                    caption += refClass.getName();
                }
                if (typeof refClassConsumer === 'function') {
                    refClassConsumer(refClass);
                }
            }
        }
        caption = caption.trim();
        if (genericType.arrayed) {
            caption += '[]';
        }
        return caption;
    },
    generateGenericTypesCaption(app, genericTypes, simple = false) {
        if (Array.isArray(genericTypes) && genericTypes.length) {
            let caption = '<';
            for (let genericType of genericTypes) {
                caption += this.generateGenericTypeCaption(app, genericType, simple) + ', ';
            }
            caption = caption.substring(0, caption.length - 2);
            return caption + '>';
        }
        return '';
    },
    /**
     * @param {ModelingApp} app 建模应用
     * @param {EnumClass} clazz 枚举模型类
     */
    generateEnumClassCode(app, clazz) {
        if (!(clazz instanceof EnumClass)) {
            throw new Error(`'The "${clazz.getName()}" is not an instance of EnumClass'`);
        }
        let codingClass = new CodingClass(clazz.packageName, clazz.simpleName);
        codingClass.kind = CodingClassKind.ENUM;
        codingClass.caption = clazz.caption;

        if (clazz.simple) {
            codingClass.annotations.push('SimpleEnumType');
            codingClass.addImportClass('org.truenewx.tnxjee.core.enums.annotation.SimpleEnumType');
        }

        if (clazz.subCaptions?.length) {
            for (let subCaption of clazz.subCaptions) {
                codingClass.annotations.push(
                    `EnumSubTypeCaption(subname = "${subCaption.subname}", caption = "${subCaption.caption}")`);
            }
            codingClass.addImportClass('org.truenewx.tnxjee.core.enums.annotation.EnumSubTypeCaption');
        }

        for (let constant of clazz.constants) {
            let codingProperty = new CodingProperty(codingClass, constant.name);
            codingProperty.constant = true;
            codingProperty.caption = constant.caption;
            if (constant.value) {
                codingProperty.annotations.push(`EnumConstantValue("${constant.value}")`);
                codingClass.addImportClass('org.truenewx.tnxjee.core.enums.annotation.EnumConstantValue');
            }
            if (constant.scenes?.length) {
                for (let scene of constant.scenes) {
                    if (scene) {
                        codingProperty.annotations.push(`EnumConstantScene(subType = "${scene}")`);
                        codingClass.addImportClass('org.truenewx.tnxjee.core.enums.annotation.EnumConstantScene');
                    }
                }
            }
            codingClass.properties.push(codingProperty);
        }

        return codingClass.toString();
    },
    addCodingProperty(app, codingClass, property, propertyClass) {
        let codingPropertyType = new CodingType(propertyClass.packageName, propertyClass.simpleName);
        codingPropertyType.arrayed = property.arrayed;
        let codingProperty = new CodingProperty(codingPropertyType, property.name);
        if (property.genericTypes?.length) {
            for (let genericType of property.genericTypes) {
                let genericTypeSimpleName = this.generateGenericTypeCaption(app, genericType, true,
                    genericTypeRefClass => {
                        codingClass.addImportClass(genericTypeRefClass);
                    });
                codingPropertyType.addGenericType(new CodingType('', genericTypeSimpleName));
            }
        }
        if (!property.derived) { // 不是派生属性才加入类中
            codingClass.properties.push(codingProperty);
        }
        return codingProperty;
    },
    createStandardCodingClass(app, clazz, options, modelInterface) {
        let codingClass = new CodingClass(clazz.packageName, clazz.simpleName);
        codingClass.caption = clazz.caption;
        codingClass.lombok = options.lombok || false;
        if (clazz.abstractClass) {
            codingClass.modifier = 'abstract';
        }
        if (clazz.superClassId) {
            let superClass = app.findClass(clazz.superClassId, true, true);
            if (superClass) {
                // 父类性质与指定类相同，则不需要再实现模型接口
                if (clazz.kind.equals(superClass.kind)) {
                    modelInterface = null;
                }
                let superCodingType = new CodingType(superClass.packageName, superClass.simpleName);
                codingClass.extends = [superCodingType];
                // 泛型具化
                if (superClass.declaredGenericTypes?.length && clazz.instantiatedGenericTypes?.length) {
                    for (let instantiatedGenericType of clazz.instantiatedGenericTypes) {
                        let codingGenericTypeSimpleName = this.generateGenericTypeCaption(app, instantiatedGenericType,
                            true, refClass => {
                                codingClass.addImportClass(refClass);
                            });
                        superCodingType.addGenericType(new CodingType('', codingGenericTypeSimpleName));
                    }
                }
            }
        }
        if (modelInterface) {
            codingClass.implements = [modelInterface];
        }
        return codingClass;
    },
    addStandardCodingProperties(app, clazz, codingClass, propertyPredicate) {
        for (let property of clazz.properties) {
            let propertyClass = app.findClass(property.typeId, true, true);
            if (typeof propertyPredicate !== 'function' || propertyPredicate(property, propertyClass)) {
                let codingProperty = this.addCodingProperty(app, codingClass, property, propertyClass);
                codingProperty.caption = property.caption;

                // getter/setter
                let getterMethod = null;
                let setterMethod = null;
                if (!codingClass.lombok) {
                    getterMethod = codingProperty.getter();
                    if (getterMethod) {
                        if (property.derived) {
                            let defaultValue = 'null';
                            if (property.typeId === 'boolean') {
                                defaultValue = 'false';
                            } else if (propertyClass.kind === TypeKind.PRIMITIVE) {
                                defaultValue = '0';
                            }
                            getterMethod.body = '// TODO 待实现\n' +
                                `    return ${defaultValue};`;
                        } else if (property.primary && TypeKind.UNITY.equals(clazz.kind)) {
                            // 单体的主键属性添加@Override注解
                            getterMethod.annotations.push('Override');
                        }
                        codingClass.methods.push(getterMethod);
                    }
                    setterMethod = codingProperty.setter();
                    if (setterMethod) {
                        if (property.derived) {
                            setterMethod.body = '// TODO 待实现';
                        }
                        codingClass.methods.push(setterMethod);
                    }
                }

                if (property.typeId === STRING_CLASS_NAME && property.mapping && !property.mapping.nullable) {
                    let notBlankAnnotationClass = new CodingType('javax.validation.constraints.NotBlank');
                    codingClass.addImportClass(notBlankAnnotationClass);
                    if (property.derived) {
                        if (setterMethod) {
                            setterMethod.annotations.push(notBlankAnnotationClass.simpleName);
                        }
                    } else {
                        codingProperty.annotations.push(notBlankAnnotationClass.simpleName);
                    }
                }
                if (property.manualInput) {
                    let notContainsAnnotationClass = new CodingType(
                        'org.truenewx.tnxjee.model.validation.constraint.NotContainsSpecialChars');
                    codingClass.addImportClass(notContainsAnnotationClass);
                    if (property.derived) {
                        if (setterMethod) {
                            setterMethod.annotations.push(notContainsAnnotationClass.simpleName);
                        }
                    } else {
                        codingProperty.annotations.push(notContainsAnnotationClass.simpleName);
                    }
                }
                if (property.regional) {
                    let regionCodeAnnotationClass = new CodingType(
                        'org.truenewx.tnxjee.model.validation.constraint.RegionCode');
                    codingClass.addImportClass(regionCodeAnnotationClass);
                    if (property.derived) {
                        if (getterMethod) {
                            getterMethod.annotations.push(regionCodeAnnotationClass.simpleName);
                        }
                    } else {
                        codingProperty.annotations.push(regionCodeAnnotationClass.simpleName);
                    }
                }
            }
        }
    },
    generateValueClassCode(app, clazz, options) {
        if (!(clazz instanceof ValueClass)) {
            throw new Error(`'The "${clazz.getName()}" is not an instance of ValueClass'`);
        }
        let codingClass = this.createStandardCodingClass(app, clazz, options,
            new CodingType('org.truenewx.tnxjee.model.ValueModel'));
        this.addStandardCodingProperties(app, clazz, codingClass);
        codingClass.addImportClass('java.util.Objects');

        if (clazz.properties.length) {
            // hashCode方法
            let hashCodeMethod = new CodingMethod('hashCode');
            hashCodeMethod.annotations = ['Override'];
            hashCodeMethod.returnType = new CodingType('int');
            hashCodeMethod.body = `return Objects.hash(${clazz.properties.filter(p => !p.derived)
                .map(p => 'this.' + p.name)
                .join(', ')});`;
            codingClass.methods.push(hashCodeMethod);

            // equals方法
            let equalsMethod = new CodingMethod('equals');
            equalsMethod.annotations = ['Override'];
            equalsMethod.returnType = new CodingType('boolean');
            equalsMethod.parameters = [new CodingMethodParameter(new CodingType('java.lang.Object'), 'o')];
            equalsMethod.body = 'if (this == o) {\n' +
                '        return true;\n' +
                '    }\n' +
                '    if (o == null || getClass() != o.getClass()) {\n' +
                '        return false;\n' +
                '    }\n' +
                `    ${clazz.simpleName} other = (${clazz.simpleName}) o;\n` +
                `    return ${clazz.properties.filter(p => !p.derived)
                    .map(p => `Objects.equals(this.${p.name}, other.${p.name})`)
                    .join('\n            && ')};`;
            codingClass.methods.push(equalsMethod);
        }

        return codingClass.toString();
    },
    generatePlainClassCode(app, clazz, options) {
        if (!(clazz instanceof PlainClass)) {
            throw new Error(`'The "${clazz.getName()}" is not an instance of PlainClass'`);
        }
        let codingClass = this.createStandardCodingClass(app, clazz, options);
        if (clazz.declaredGenericTypes?.length) {
            codingClass.addGenericType(...clazz.declaredGenericTypes.map(genericType => {
                let packageName = '';
                let simpleName = this.generateGenericTypeCaption(app, genericType, true, refClass => {
                    packageName = refClass.packageName;
                    codingClass.addImportClass(refClass);
                });
                return {
                    packageName: packageName,
                    simpleName: simpleName,
                };
            }));
        }
        this.addStandardCodingProperties(app, clazz, codingClass);
        return codingClass.toString();
    },
    getPath(root, mavenProject, clazz, codeType) {
        const getJavaPath = function (layer, {packageName, simpleName}) {
            return tnx.util.file.joinPath(root, mavenProject.artifactId + '-' + layer, 'src', 'main', 'java',
                packageName.replace(/\./g, tnx.util.file.separator), simpleName + '.java');
        }
        switch (codeType) {
            case 'sql': {
                return tnx.util.file.joinPath(root, mavenProject.artifactId + '-service',
                    'src', 'test', 'resources', 'sql', 'full', 'schema.sql');
            }
            case 'jpa': {
                // TODO 暂时固定目录为/jpa，后续从配置文件中读取
                let path = tnx.util.file.joinPath(root, mavenProject.artifactId + '-repo',
                    'src', 'main', 'resources', 'jpa');
                let moduleName = BaseType.getModuleName(clazz.packageName);
                if (moduleName) {
                    path = tnx.util.file.joinPath(path, moduleName);
                }
                return tnx.util.file.joinPath(path, clazz.simpleName + '.xml');
            }
            case 'converter': {
                return getJavaPath('repo', clazz);
            }
            case 'querying': {
                return getJavaPath('model', {
                    packageName: this.getQueryingPackageName(clazz),
                    simpleName: this.getQueryingSimpleName(clazz),
                });
            }
            case 'repo':
            case 'repox':
            case 'repoImpl': {
                return getJavaPath('repo', {
                    packageName: this.getRepoPackageName(clazz),
                    simpleName: this.getRepoSimpleName(clazz, codeType),
                });
            }
            case 'command': {
                return getJavaPath('service', {
                    packageName: this.getCommandPackageName(clazz),
                    simpleName: this.getCommandSimpleName(clazz),
                });
            }
            case 'service':
            case 'serviceImpl': {
                return getJavaPath('service', {
                    packageName: this.getServicePackageName(clazz),
                    simpleName: this.getServiceSimpleName(clazz, codeType),
                });
            }
            case 'controller': {
                return getJavaPath('web', {
                    packageName: this.getControllerPackageName(clazz),
                    simpleName: this.getControllerSimpleName(clazz),
                });
            }
        }
        return getJavaPath('model', clazz);
    },
    /**
     * @param {ModelingProject} project 建模工程
     * @param {ModelingApp} app 建模应用
     * @param {EntityClass} clazz 实体模型类
     * @param {Object} options 选项
     */
    generateEntityClassCode(project, app, clazz, options = {}) {
        if (!(clazz instanceof EntityClass)) {
            throw new Error(`'The "${clazz.getName()}" is not an instance of EntityClass'`);
        }
        let entityInterface = null;
        if (TypeKind.UNITY.equals(clazz.kind)) {
            entityInterface = new CodingType('org.truenewx.tnxjee.model.entity.unity.Unity');
        } else if (TypeKind.RELATION.equals(clazz.kind)) {
            entityInterface = new CodingType('org.truenewx.tnxjee.model.entity.relation.Relation');
        } else if (TypeKind.ENTITY.equals(clazz.kind)) {
            entityInterface = new CodingType('org.truenewx.tnxjee.model.entity.Entity');
        }
        let codingClass = this.createStandardCodingClass(app, clazz, options, entityInterface);
        // 关系，如果包含主键属性，则采用默认关系父类
        let usingDefaultRelationSuperClass = false;
        if (TypeKind.RELATION.equals(clazz.kind)) {
            usingDefaultRelationSuperClass = clazz.existsPrimaryProperty();
        }
        if (usingDefaultRelationSuperClass) {
            codingClass.extends = [new CodingType('org.truenewx.tnxjee.model.entity.relation.AbstractRelation')];
            // 使用默认关系父类时不实现接口
            codingClass.implements = [];
        }

        let primaryPropertyTypes = [];
        let primaryPropertyNames = [];
        // 在所有父类中找主键属性
        let superClasses = project.getSuperClassChain(clazz, true, true);
        for (let superClass of superClasses) {
            for (let property of superClass.properties) {
                if (property.primary) {
                    let primaryPropertyName = property.name;
                    let codingPropertyType;
                    let refClass = app.findClass(property.typeId, true, true);
                    if (refClass.kind.supports('association')) {
                        codingPropertyType = new CodingType(app.getPrimaryLogicTypeIds(refClass, true)[0]);
                        let refPrimaryPropertyName = app.getPrimaryLogicNames(refClass)[0];
                        primaryPropertyName += refPrimaryPropertyName.firstToUpperCase();
                    } else {
                        codingPropertyType = new CodingType(refClass.packageName, refClass.simpleName);
                    }
                    primaryPropertyTypes.push(codingPropertyType);
                    primaryPropertyNames.push(primaryPropertyName);
                }
            }
        }

        // 没有找到主键属性，则主键属性类型为泛型参数
        if (!primaryPropertyTypes.length) {
            if (TypeKind.UNITY.equals(clazz.kind)) {
                primaryPropertyTypes.push(new CodingType('', 'K'));
                codingClass.addGenericType(new CodingType('', 'K extends Serializable'));
                codingClass.addImportClass('java.io.Serializable');
            } else if (TypeKind.RELATION.equals(clazz.kind)) {
                primaryPropertyTypes.push(new CodingType('', 'L'));
                codingClass.addGenericType(new CodingType('', 'L extends Serializable'));
                primaryPropertyTypes.push(new CodingType('', 'R'));
                codingClass.addGenericType(new CodingType('', 'R extends Serializable'));
                codingClass.addImportClass('java.io.Serializable');
            }
        }

        // 继承默认关系，则创建内部的关键属性类
        if (usingDefaultRelationSuperClass) {
            let innerClass = new CodingClass('', 'Key');
            innerClass.lombok = codingClass.lombok;
            innerClass.modifier = 'static';
            let superCodingType = new CodingType('org.truenewx.tnxjee.model.entity.relation.AbstractRelationKey');
            superCodingType.addGenericType(...primaryPropertyTypes);
            innerClass.extends = [superCodingType];

            for (let i = 0; i < primaryPropertyNames.length; i++) {
                let primaryPropertyName = primaryPropertyNames[i];
                let primaryPropertyType = primaryPropertyTypes[i];
                let primaryProperty = new CodingProperty(primaryPropertyType, primaryPropertyName);
                innerClass.properties.push(primaryProperty);
                if (!codingClass.lombok) {
                    innerClass.methods.push(primaryProperty.getter(), primaryProperty.setter());
                }
            }

            let getLeftMethod = new CodingMethod('getLeft');
            getLeftMethod.annotations.push('Override');
            getLeftMethod.returnType = primaryPropertyTypes[0];
            getLeftMethod.body = `return this.${primaryPropertyNames[0]};`;
            innerClass.methods.push(getLeftMethod);

            let getRightMethod = new CodingMethod('getRight');
            getRightMethod.annotations.push('Override');
            getRightMethod.returnType = primaryPropertyTypes[1];
            getRightMethod.body = `return this.${primaryPropertyNames[1]};`;
            innerClass.methods.push(getRightMethod);

            codingClass.inner = innerClass;

            let innerType = new CodingType(innerClass.packageName, innerClass.simpleName);
            let idProperty = new CodingProperty(innerType, 'id');
            codingClass.properties.push(idProperty);
            if (!codingClass.lombok) {
                let getIdMethod = idProperty.getter();
                getIdMethod.annotations.push('Override', 'SuppressWarnings("unchecked")');
                codingClass.methods.push(getIdMethod);
                codingClass.methods.push(idProperty.setter());
            }
        }

        this.addStandardCodingProperties(app, clazz, codingClass, (property, propertyClass) => {
            // 继承默认关系，则忽略非单体类型的主键属性
            return !(usingDefaultRelationSuperClass && property.primary && !TypeKind.UNITY.equals(propertyClass.kind));
        });

        // 决定性质的父类或接口使用主键泛型具化
        let kindType = usingDefaultRelationSuperClass ? codingClass.extends[0] : entityInterface;
        if (kindType) {
            for (let primaryPropertyType of primaryPropertyTypes) {
                kindType.addGenericType(primaryPropertyType);
            }
        }

        return codingClass.toString();
    },
    generateSqlCode(project, app, clazz, superClass) {
        if (clazz.abstractClass && (!clazz.inheritanceStrategy || InheritanceStrategy.TABLE_PER_CLASS.equals(
            clazz.inheritanceStrategy))) {
            return '';
        }

        // 如果父类的继承映射策略为共用单表，则生成父类的建表SQL
        if (InheritanceStrategy.SINGLE_TABLE.equals(superClass?.inheritanceStrategy)) {
            clazz = superClass;
        }

        const getFieldSql = function (field) {
            sql = `${field.name} ${field.type}`;
            if (field.length) {
                sql += '(' + field.length;
                if (field.scale) {
                    sql += ', ' + field.scale;
                }
                sql += ')';
            }
            if (!field.nullable) {
                sql += ' not null';
            }
            if (field.defaultValue) {
                sql += ` default ${field.defaultValue}`;
            }
            return sql;
        }

        let tableName = app.getTableName(clazz);
        let pkFieldNames = [];
        let fkFieldTableMapping = {};
        let properties = clazz.properties.filter(p => !p.derived);
        let indexes = [...clazz.indexes];
        // 共用单表的继承映射策略下，汇集所有子类中的属性，以及子类的索引
        if (InheritanceStrategy.SINGLE_TABLE.equals(clazz.inheritanceStrategy)) {
            // 插入鉴定字段
            let discriminatorColumn = clazz.discriminatorColumn;
            properties.push({
                mapping: {
                    name: discriminatorColumn.name,
                    type: discriminatorColumn.type.physicalType,
                    length: discriminatorColumn.length,
                    nullable: false,
                },
            });

            project.loopSubClasses(clazz, subClass => {
                properties.push(...subClass.properties);
                indexes.push(...subClass.indexes);
            });
        } else {
            // 如果父类未指定继承映射策略，或策略为每类一表，则需加入所有各级父类的非主键字段
            if (!superClass?.inheritanceStrategy
                || InheritanceStrategy.TABLE_PER_CLASS.equals(superClass.inheritanceStrategy)) {
                let superClasses = project.getSuperClassChain(clazz);
                for (let i = superClasses.length - 1; i >= 0; i--) {
                    properties.unshift(...superClasses[i].properties.filter(p => !p.primary));
                }
            }
        }
        // 如果当前类中没有主键属性，则从父类中取主键属性
        if (!properties.some(p => p.primary)) {
            let superClasses = project.getSuperClassChain(clazz);
            for (let i = superClasses.length - 1; i >= 0; i--) {
                let primaryProperties = superClasses[i].properties.filter(p => p.primary);
                if (primaryProperties.length) {
                    properties.unshift(...primaryProperties);
                    break;
                }
            }
        }

        let sql = 'create table ' + tableName + ' (\n';
        for (let property of properties) {
            if (property.embedded?.length) {
                for (let embeddedMapping of property.embedded) {
                    sql += '    ' + getFieldSql(embeddedMapping) + ',\n';
                }
            } else {
                let mapping = property.mapping;
                if (mapping) {
                    sql += '    ' + getFieldSql(mapping);
                    if (property.primary) {
                        pkFieldNames.push(mapping.name);
                        if ((mapping.type === 'bigint' || mapping.type === 'int')
                            && !InheritanceStrategy.JOINED.equals(superClass?.inheritanceStrategy)) {
                            sql += ' auto_increment';
                        }
                    }
                    sql += ',\n';
                    let refClass = this.getPropertyAssociationClass(app, property);
                    if (refClass) {
                        fkFieldTableMapping[mapping.name] = app.getTableName(refClass);
                    }
                } else {
                    sql += `    -- 属性 ${property.name} 缺少必要的物理字段设置，请先予以设置保存后再来重试\n`;
                }
            }
        }
        sql += '\n';

        // 基础表名
        let baseTableName = app.tableNameRule.getBaseTableName(tableName);
        // 主键约束
        sql += '    constraint pk_' + baseTableName + ' primary key (' + pkFieldNames.join(', ') + '),\n';
        // 外键约束
        // 父类的继承映射策略为联合表时，子类表id外键关联父类表id
        if (InheritanceStrategy.JOINED.equals(superClass?.inheritanceStrategy)) {
            let superTableName = app.getTableName(superClass);
            let fkFieldName = 'id';
            sql += '    constraint fk_' + baseTableName + '_' + fkFieldName
                + ' foreign key (' + fkFieldName + ') references ' + superTableName + ' (id),\n';
        }
        for (let fkFieldName in fkFieldTableMapping) {
            sql += '    constraint fk_' + baseTableName + '_' + fkFieldName
                + ' foreign key (' + fkFieldName + ') references ' + fkFieldTableMapping[fkFieldName] + ' (id),\n';
        }
        // 索引
        for (let index of indexes) {
            let indexFieldNames = clazz.getMappingNames(index.mappingIds);
            sql += '    ';
            if (indexFieldNames.length) {
                if (PhysicalIndexType.UNIQUE.equals(index.type)) {
                    sql += 'unique ';
                }
                sql += `index ${index.name} (${indexFieldNames.join(', ')}),\n`;
            } else {
                sql += `-- 索引 ${index.name} 缺少必要的物理字段设置，请先予以设置保存后再来重试\n`;
            }
        }

        // 替换最后的,为;
        let index = sql.lastIndexOf(',\n');
        sql = sql.substring(0, index) + '\n' + sql.substring(index + 2);
        sql += ');\n';

        return sql;
    },
    getAutoEnumConverters(app, clazz) {
        let converters = {};
        for (let property of clazz.properties) {
            let propertyClass = app.findClass(property.typeId, true, true);
            if (TypeKind.ENUM.equals(propertyClass.kind)) {
                let converterClassName = this.getEnumConverterClassName(propertyClass, property.arrayed);
                if (!converters[converterClassName]) {
                    converters[converterClassName] = {
                        enumClass: propertyClass,
                        arrayed: property.arrayed,
                    };
                }
            }
        }
        return converters;
    },
    getPropertyAssociationClass(app, property) {
        // 长度为32的typeId才可能是关联的建模类，否则一定是固定依赖库中的类型
        if (property.typeId && property.typeId.length === 32) {
            let refClass = app.findClass(property.typeId, true, true);
            if (refClass && refClass.kind.supports('association')) {
                return refClass;
            }
        }
        return null;
    },
    generateJpaMappingXmlCode(app, clazz, superClass) {
        let xml = '<?xml version="1.0" encoding="UTF-8"?>\n' +
            '<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"\n' +
            '        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.2"\n' +
            '        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm http://xmlns.jcp.org/xml/ns/persistence/orm_2_2.xsd">\n\n' +
            '    <persistence-unit-metadata>\n' +
            '        <persistence-unit-defaults>\n' +
            '            <access>FIELD</access>\n' +
            '        </persistence-unit-defaults>\n' +
            '    </persistence-unit-metadata>\n\n';
        let entityTagName = DEFAULT_ENTITY_TAG_NAME;
        // 抽象实体类未指定继承映射策略时，采用<mapped-superclass>
        if (clazz.abstractClass && !clazz.inheritanceStrategy) {
            entityTagName = 'mapped-superclass';
        }
        xml += `    <${entityTagName} class="${clazz.getName()}"`;
        if (entityTagName === DEFAULT_ENTITY_TAG_NAME && clazz.abstractClass) {
            xml += ' abstract="true"';
        }
        xml += '>\n';
        // 实体类不是共用单表的继承映射策略下的子类，则生成表名标签
        if (entityTagName === DEFAULT_ENTITY_TAG_NAME) {
            let existsTable = true;
            if (superClass?.inheritanceStrategy) {
                // 如果父类指定了继承映射策略为共用单表，则当前类没有对应数据库表
                existsTable = !InheritanceStrategy.SINGLE_TABLE.equals(superClass.inheritanceStrategy);
            } else if (clazz.inheritanceStrategy) {
                // 否则，如果当前类指定了继承映射策略，则当前类不是抽象类时才存在对应表
                existsTable = !clazz.abstractClass;
            }
            if (existsTable) {
                xml += `        <table name="${app.getTableName(clazz)}"/>\n`;
            }
        }
        if (clazz.inheritanceStrategy) {
            xml += `        <inheritance strategy="${clazz.inheritanceStrategy}"/>\n`;
            if (InheritanceStrategy.SINGLE_TABLE.equals(clazz.inheritanceStrategy)) {
                let discriminatorColumn = clazz.discriminatorColumn;
                xml += `        <discriminator-column name="${discriminatorColumn.name}"`
                    + ` discriminator-type="${discriminatorColumn.type}"`
                    + ` length="${discriminatorColumn.length}"/>\n`;
            }
        }
        if (superClass) {
            if (InheritanceStrategy.SINGLE_TABLE.equals(superClass.inheritanceStrategy)) {
                xml += `        <discriminator-value>${clazz.discriminatorValue}</discriminator-value>\n`;
            }
        }
        xml += '        <attributes>\n';
        let handledPropertyNames = new Set();
        let converters = {};
        // 主键
        if (TypeKind.RELATION.equals(clazz.kind)) {
            xml += '            <embedded-id name="id">\n';
            for (let property of clazz.properties) {
                if (property.primary) {
                    let propertyName = property.name;
                    let refClass = this.getPropertyAssociationClass(app, property);
                    if (refClass) {
                        let refPrimaryProperty = refClass.properties.find(p => p.primary);
                        propertyName += refPrimaryProperty.name.firstToUpperCase();
                    }
                    xml += `                <attribute-override name="${propertyName}">\n` +
                        `                    <column name="${property.mapping.name}"/>\n` +
                        '                </attribute-override>\n';
                    handledPropertyNames.add(propertyName);
                }
            }
            xml += '            </embedded-id>\n';
        } else {
            let primaryProperty = clazz.getPrimaryProperty();
            if (!primaryProperty && InheritanceStrategy.TABLE_PER_CLASS.equals(clazz.inheritanceStrategy)) {
                primaryProperty = superClass.getPrimaryProperty();
            }
            if (primaryProperty) {
                let mapping = primaryProperty.mapping;
                xml += `            <id name="${primaryProperty.name}">\n` +
                    `                <column name="${mapping.name}"/>\n`;
                // 主键属性可能设置在子类，此时如果父类的继承映射策略为每类一表策略，也需要生成主键生成策略
                let inheritanceStrategy = superClass?.inheritanceStrategy || clazz.inheritanceStrategy;
                if (InheritanceStrategy.TABLE_PER_CLASS.equals(inheritanceStrategy)) {
                    xml +=
                        `                <!-- “${clazz.inheritanceStrategy.caption}”继承映射策略下，为避免主键冲突，不能使用自增策略，请确认或调整主键生成策略 -->\n` +
                        '                <generated-value strategy="AUTO"/>\n';
                } else if (mapping.type === 'int' || mapping.type === 'bigint') {
                    xml += '                <generated-value strategy="IDENTITY"/>\n';
                }
                xml += '            </id>\n';
                handledPropertyNames.add(primaryProperty.name);
            }
        }

        const getColumnXml = function (app, propertyTypeId, propertyArrayed, mapping) {
            if (!mapping.type) {
                throw new Error(`${mapping.logicPropertyName} 未指定物理字段类型，请先保存实体后再重试`);
            }
            let xml = `<column name="${mapping.name}" nullable="${mapping.nullable}"`;
            let physicalType = PersistUtil.findPhysicalType(app, mapping.type);
            if (!physicalType) {
                throw new Error('未知的物理字段类型：' + mapping.type);
            }
            if (!PhysicalTypeNecessity.NONE.equals(physicalType.scaleNecessity)) {
                xml += ` precision="${mapping.length}" scale="${mapping.scale}"`;
            } else if (!PhysicalTypeNecessity.NONE.equals(physicalType.lengthNecessity)) {
                xml += ` length="${mapping.length}"`;
            }
            // char类型或不是默认的物理类型，需加上column-definition属性
            let defaultPhysicalType = PersistUtil.getDefaultPhysicalType(app, propertyTypeId, propertyArrayed);
            if (mapping.type === 'char' || mapping.type !== defaultPhysicalType?.name) {
                xml += ` column-definition="${mapping.type}"`;
            }
            xml += '/>\n';
            return xml;
        }

        const getActualConverter = (converter, property) => {
            if (converter === EntityPropertyMapping.CONVERTER_AUTO) {
                let propertyClass = app.findClass(property.typeId, true, true);
                if (TypeKind.ENUM.equals(propertyClass.kind)) { // 生成枚举类型转换器配置
                    converter = this.getEnumConverterClassName(propertyClass, property.arrayed);
                    if (!converters[converter]) {
                        converters[converter] = {
                            enumClass: propertyClass,
                            arrayed: property.arrayed,
                        };
                    }
                }
            }
            return converter;
        }

        // 普通属性
        for (let property of clazz.properties) {
            if (property.derived) {
                handledPropertyNames.add(property.name);
            } else if (!property.primary && !property.embedded?.length) {
                let associationClass = this.getPropertyAssociationClass(app, property);
                if (!associationClass) {
                    xml += `            <basic name="${property.name}">\n`;
                    xml += '                ' + getColumnXml(app, property.typeId, property.arrayed,
                        property.mapping);
                    let converter = property.mapping?.converter;
                    if (converter) { // 指定了转换器
                        converter = getActualConverter(converter, property);
                        xml += `                <convert converter="${converter}"/>\n`;
                    }
                    xml += '            </basic>\n';
                    handledPropertyNames.add(property.name);
                }
            }
        }
        // 内嵌值模型属性
        for (let property of clazz.properties) {
            if (property.embedded?.length) {
                let valueClass = app.findClass(property.typeId, true, true);
                xml += `            <embedded name="${property.name}">\n`;
                // 必须按照普通属性、关联属性、转换属性的顺序生成
                let valueProperties = [];
                // 值模型中的普通属性
                for (let embeddedField of property.embedded) {
                    let valueProperty = valueClass.properties.find(p => p.id === embeddedField.logicPropertyId);
                    let valuePropertyClass = app.findClass(valueProperty.typeId, true, true);
                    valueProperty.refKind = valuePropertyClass.kind;
                    valueProperties.push(valueProperty); // 缓存以便于后续使用
                    // 不是可关联类型，也没有指定转换器，则为普通属性
                    if (!valueProperty.refKind.supports('association') && !embeddedField.converter) {
                        xml += `                <attribute-override name="${valueProperty.name}">\n` +
                            '                    ' + getColumnXml(app, valueProperty.typeId,
                                valueProperty.arrayed, embeddedField) +
                            '                </attribute-override>\n';
                    }
                }
                // 值模型中的关联属性
                for (let i = 0; i < property.embedded.length; i++) {
                    let valueProperty = valueProperties[i];
                    if (valueProperty.refKind.supports('association')) {
                        let embeddedField = property.embedded[i];
                        xml += `                <association-override name="${valueProperty.name}">\n` +
                            `                    <join-column name="${embeddedField.name}"/>\n` +
                            '                </association-override>\n';
                    }
                }
                // 值模型中的转换属性
                for (let i = 0; i < property.embedded.length; i++) {
                    let embeddedMapping = property.embedded[i];
                    let converter = embeddedMapping.converter;
                    if (converter) {
                        let valueProperty = valueProperties[i];
                        converter = getActualConverter(converter, valueProperty);
                        xml += `                <convert attribute-name="${valueProperty.name}"`
                            + ` converter="${converter}"/>\n`;
                    }
                }
                xml += '            </embedded>\n';
                handledPropertyNames.add(property.name);
            }
        }
        // 引用属性
        for (let property of clazz.properties) {
            if (!property.embedded?.length) {
                let associationClass = this.getPropertyAssociationClass(app, property);
                if (associationClass) {
                    let mapping = property.mapping;
                    xml += `            <many-to-one name="${property.name}" optional="${mapping.nullable}"`;
                    // 关系中的主键属性允许引用类型
                    if (property.primary && TypeKind.RELATION.equals(clazz.kind)) {
                        let refPrimaryProperty = associationClass.properties.find(p => p.primary);
                        xml += ` maps-id="${property.name}${refPrimaryProperty.name.firstToUpperCase()}"`;
                    }
                    xml += '>\n';
                    xml += `                <join-column name="${mapping.name}"/>\n`;
                    xml += '            </many-to-one>\n';
                    handledPropertyNames.add(property.name);
                } else {
                    let refClass = app.getCollectionElementRefClass(property);
                    if (refClass) {
                        let associationProperty = refClass.properties.find(p => p.typeId === clazz.id);
                        if (associationProperty) {
                            xml +=
                                `            <one-to-many name="${property.name}" mapped-by="${associationProperty.name}"/>\n`;
                        } else {
                            xml += `            <one-to-many name="${property.name}">\n`;
                            xml += `                <join-column name="${property.mapping.name}"/>\n`;
                            xml += '            </one-to-many>\n';
                        }
                        handledPropertyNames.add(property.name);
                    }
                }
            }
        }
        // 未生成配置的属性
        for (let property of clazz.properties) {
            if (!handledPropertyNames.has(property.name)) {
                xml += `            <!-- 属性 ${property.name} 不符合标准规范，请手工配置 -->\n`;
            }
        }
        xml += '        </attributes>\n' +
            `    </${entityTagName}>\n\n` +
            '</entity-mappings>\n';
        return {
            code: xml,
            converters: converters,
        };
    },
    getEnumConverterClassName(enumClass, arrayed) {
        let packageNameArray = enumClass.packageName.split('.');
        if (packageNameArray[packageNameArray.length - 1] === 'enums') {
            packageNameArray.pop();
        }
        // 找到model替换为repo
        if (packageNameArray[packageNameArray.length - 1] === 'model') {
            packageNameArray[packageNameArray.length - 1] = 'repo';
        } else {
            for (let i = 0; i < packageNameArray.length; i++) {
                if (packageNameArray[i] === 'model') {
                    packageNameArray[i] = 'repo';
                }
            }
        }
        return packageNameArray.join('.') + '.converter.' + enumClass.simpleName + (arrayed ? 'Array' : '')
            + 'Converter';
    },
    generateEnumConverterCode(enumClass, arrayed) {
        let className = this.getEnumConverterClassName(enumClass, arrayed);
        let codingClass = new CodingClass(className);
        let superClassName = arrayed ? 'org.truenewx.tnxjee.repo.jpa.converter.EnumArrayAttributeConverter'
            : 'org.truenewx.tnxjee.repo.jpa.converter.EnumAttributeConverter';
        let superCodingType = new CodingType(superClassName);
        superCodingType.addGenericType(enumClass);
        codingClass.extends = [superCodingType];
        return codingClass.toString();
    },
    getLayerPackageName(entityClassPackageName = '', layer) {
        let packages = entityClassPackageName.split('.');
        if (packages.length === 0) {
            return '';
        }
        // 去掉末尾可能的entity包
        if (packages[packages.length - 1] === 'entity') {
            packages.pop();
        }
        // 替换靠后的model为层级名
        for (let i = packages.length - 1; i >= 0; i--) {
            if (packages[i] === 'model') {
                packages[i] = layer;
                break;
            }
        }
        return packages.join('.');
    },
    getDefaultExtended(app, clazz) {
        let superClass = app.findClass(clazz.superClassId, true, true);
        // 如果父类是抽象类，则子类的系列类默认继承父类的系列类
        return !!superClass?.abstractClass;
    },
    getQueryingPackageName(entityClass) {
        return this.getLayerPackageName(entityClass.packageName, 'model') + '.query';
    },
    getQueryingSimpleName(entityClass) {
        return entityClass.simpleName + 'Querying';
    },
    getDefaultModelPropertyName(app, propertyName, propertyClass) {
        let name = propertyName;
        if (propertyClass && propertyClass.kind.supports('association')) {
            let primaryPropertyName = app.getPrimaryLogicNames(propertyClass)[0];
            name += primaryPropertyName.firstToUpperCase();
        }
        return name;
    },
    getDefaultEmbeddedModelPropertyName(app, mainPropertyName, valuePropertyName, valuePropertyClass) {
        let queryingPropertyName = this.getDefaultModelPropertyName(app, valuePropertyName, valuePropertyClass);
        return mainPropertyName + queryingPropertyName.firstToUpperCase();
    },
    addModelCodingProperty(app, options, codingClass, model, property, propertyName, propertyClass) {
        let modelProperty = Object.assign({}, property, {
            name: propertyName,
        });
        let modelPropertyClass;
        if (model.typeId) {
            modelPropertyClass = app.findClass(model.typeId, true, true);
            // 自定义了类型，则将可能的自定义泛型和是否数组赋予查询模型属性
            if (model.genericTypes?.length) {
                modelProperty.genericTypes = model.genericTypes;
            }
            if (typeof model.arrayed === 'boolean') {
                modelProperty.arrayed = model.arrayed;
            }
        } else if (propertyClass) { // 默认类型如果是关联实体，则取其主键属性类型
            if (propertyClass.kind.supports('association')) {
                let primaryLogicTypeId = app.getPrimaryLogicTypeIds(propertyClass)[0];
                modelPropertyClass = app.findClass(primaryLogicTypeId, true, true);
            } else {
                modelPropertyClass = propertyClass;
            }
        }
        let codingProperty = this.addCodingProperty(app, codingClass, modelProperty,
            modelPropertyClass);
        codingProperty.caption = model.caption;
        if (!options.lombok) {
            codingClass.methods.push(codingProperty.getter(), codingProperty.setter());
        }
    },
    addModelCodingProperties(app, clazz, options, codingClass, modelType) {
        // 非继承模式时，递归加入父类的模型属性
        if (!options.extended && clazz.superClassId) {
            let superClass = app.findClass(clazz.superClassId, true, true);
            if (superClass) {
                this.addModelCodingProperties(app, superClass, options, codingClass, modelType);
            }
        }
        if (clazz.properties) {
            for (let property of clazz.properties) {
                let propertyClass = app.findClass(property.typeId, true, true);

                if (propertyClass instanceof ValueClass) { // 值模型属性
                    for (let embeddedMapping of property.embedded) {
                        let model = embeddedMapping[modelType];
                        if (model) {
                            let valueProperty = propertyClass.getPropertyById(embeddedMapping.logicPropertyId);
                            if (valueProperty) {
                                let valuePropertyClass = app.findClass(valueProperty.typeId, true, true);
                                let modelPropertyName = model.name || this.getDefaultEmbeddedModelPropertyName(app,
                                    property.name, valueProperty.name, valuePropertyClass);
                                this.addModelCodingProperty(app, options, codingClass, model, property,
                                    modelPropertyName, valuePropertyClass);
                            }
                        }
                    }
                } else if (property.mapping) { // 普通属性
                    let model = property.mapping[modelType];
                    if (model) {
                        let modelPropertyName = model.name
                            || this.getDefaultModelPropertyName(app, property.name, propertyClass);
                        this.addModelCodingProperty(app, options, codingClass, model, property,
                            modelPropertyName, propertyClass);
                    }
                }
            }
        }
    },
    generateQueryingCode(app, clazz, options = {}) {
        let packageName = this.getQueryingPackageName(clazz);
        let simpleName = this.getQueryingSimpleName(clazz);
        let codingClass = new CodingClass(packageName, simpleName);
        codingClass.lombok = options.lombok || false;
        if (clazz.abstractClass) {
            codingClass.modifier = 'abstract';
        }
        let keywordInvolved = clazz.isKeywordInvolved();
        if (options.extended) {
            let superClass = app.findClass(clazz.superClassId, true, true);
            if (superClass) {
                let superCodingType = new CodingType(this.getQueryingPackageName(superClass),
                    this.getQueryingSimpleName(superClass));
                codingClass.extends = [superCodingType];
                // 如果父类是实体类，且是涉及关键搜索的，则当前类的查询模型无需再生成关键字属性
                if (superClass instanceof EntityClass && superClass.isKeywordInvolved()) {
                    keywordInvolved = false;
                }
            }
        }
        if (!codingClass.extends.length) {
            codingClass.extends = [new CodingType('org.truenewx.tnxjee.model.query.Querying')];
        }

        this.addModelCodingProperties(app, clazz, options, codingClass, 'querying');

        // 最后生成关键字属性
        if (keywordInvolved) {
            this.addModelCodingProperty(app, options, codingClass, {
                typeId: STRING_CLASS_NAME,
            }, {
                caption: '关键字',
            }, KEYWORD_NAME);
        }

        return codingClass.toString();
    },
    getRepoPackageName(entityClass) {
        return this.getLayerPackageName(entityClass.packageName, 'repo');
    },
    getRepoSimpleName(entityClass, repoType = 'repo') {
        let simpleName;
        if (repoType === 'repoImpl' && entityClass.abstractClass) {
            simpleName = entityClass.simpleName + this.getEntityCodeTypeItem('repo').caption + 'Support';
        } else {
            simpleName = entityClass.simpleName + this.getEntityCodeTypeItem(repoType).caption;
        }
        return simpleName;
    },
    generateRepoCode(app, clazz, options = {}) {
        let codingClass = this.createRepoCodingClass(app, clazz, options, 'repo');
        codingClass.kind = CodingClassKind.INTERFACE;
        if (!codingClass.extends.length) {
            let superCodingType = new CodingType('org.springframework.data.jpa.repository.JpaRepository');
            superCodingType.addGenericType(clazz);
            if (TypeKind.RELATION.equals(clazz.kind)) {
                superCodingType.addGenericType(new CodingType(clazz.packageName, clazz.simpleName + '.Key'));
            } else {
                let primaryLogicTypeId = app.getPrimaryLogicTypeIds(clazz)[0];
                if (primaryLogicTypeId) {
                    superCodingType.addGenericType(primaryLogicTypeId);
                } else {
                    superCodingType.addGenericType(new CodingType('', 'K'));
                    codingClass.addGenericType(new CodingType('', 'K extends Serializable'));
                    codingClass.addImportClass('java.io.Serializable');
                }
            }
            codingClass.extends = [superCodingType];
        }
        if (options.expanded) {
            codingClass.extends.push(
                new CodingType(codingClass.packageName, this.getRepoSimpleName(clazz, 'repox')));
        }

        for (let property of clazz.properties) {
            if (property.belongs) {
                let propertyClass = app.findClass(property.typeId, true, true);
                if (propertyClass && propertyClass.kind.supports('association')) {
                    let primaryLogicName = app.getPrimaryLogicNames(propertyClass)[0];
                    let propertyName = property.name + primaryLogicName.firstToUpperCase();
                    let methodName = 'findBy' + propertyName.firstToUpperCase();
                    let codingMethod = new CodingMethod(methodName);
                    let primaryLogicTypeId = app.getPrimaryLogicTypeIds(propertyClass, true)[0];
                    codingMethod.addParameter(new CodingType(primaryLogicTypeId), propertyName);
                    let returnType = new CodingType(LIST_CLASS_NAME);
                    returnType.addGenericType(new CodingType(clazz.packageName, clazz.simpleName));
                    codingMethod.returnType = returnType;
                    codingMethod.body = null;

                    codingClass.methods.push(codingMethod);
                }
            }
        }

        return codingClass.toString();
    },
    createRepoCodingClass(app, clazz, options, repoType) {
        let packageName = this.getRepoPackageName(clazz);
        let simpleName = this.getRepoSimpleName(clazz, repoType);
        let codingClass = new CodingClass(packageName, simpleName);
        if (options.extended) {
            let superClass = app.findClass(clazz.superClassId, true, true);
            if (superClass) {
                let superCodingType = new CodingType(this.getRepoPackageName(superClass),
                    this.getRepoSimpleName(superClass, repoType));
                // 父类如果为抽象类而当前类不是抽象类，则大概率需指定泛型为当前实体类
                if (superClass.abstractClass && !clazz.abstractClass) {
                    superCodingType.addGenericType(new CodingType(clazz.packageName, clazz.simpleName));
                }
                codingClass.extends = [superCodingType];
            }
        }
        return codingClass;
    },
    generateRepoxCode(app, clazz, options = {}) {
        let codingClass = this.createRepoCodingClass(app, clazz, options, 'repox');
        codingClass.kind = CodingClassKind.INTERFACE;

        if (options.queryable) {
            let queryMethod = this.addRepoQueryMethod(clazz, codingClass);
            queryMethod.modifier = ''; // 接口方法没有修饰符
            queryMethod.body = null; // 不生成方法体
        }

        return codingClass.toString();
    },
    addRepoQueryMethod(clazz, codingClass) {
        let queryMethod = new CodingMethod('query');
        queryMethod.returnType = new CodingType('org.truenewx.tnxjee.model.query.QueryResult');
        codingClass.addImportClass(queryMethod.returnType);
        let queryingPackageName = this.getQueryingPackageName(clazz);
        let queryingSimpleName = this.getQueryingSimpleName(clazz);
        let queryingType = new CodingType(queryingPackageName, queryingSimpleName);
        queryMethod.addParameter(queryingType, 'querying');
        codingClass.addImportClass(queryingType);
        let entityClassType = new CodingType(clazz.packageName, clazz.simpleName);
        queryMethod.returnType.genericTypes = [entityClassType];
        codingClass.methods = [queryMethod];
        codingClass.addImportClass(entityClassType);
        return queryMethod;
    },
    createSuperCodingType(app, clazz, codingClass, superClassName) {
        let superCodingType = new CodingType(superClassName);
        if (superClassName !== 'org.truenewx.tnxjee.service.Service') { // Service接口没有泛型声明
            superCodingType.addGenericType(new CodingType(clazz.packageName, clazz.simpleName));
            let primaryLogicTypeIds = app.getPrimaryLogicTypeIds(clazz);
            if (TypeKind.UNITY.equals(clazz.kind)) { // 单体固定只有一个主键属性，且类型受限，可简单处理，以节省性能
                let primaryLogicTypeId = primaryLogicTypeIds[0];
                if (primaryLogicTypeId) {
                    superCodingType.addGenericType(primaryLogicTypeId);
                } else { // 泛型主键类型
                    superCodingType.addGenericType(new CodingType('', 'K'));
                    codingClass.addGenericType(new CodingType('', 'K extends Serializable'));
                    codingClass.addImportClass('java.io.Serializable');
                }
            } else if (TypeKind.RELATION.equals(clazz.kind)) {
                for (let i = 0; i < 2; i++) {
                    let primaryLogicTypeId = primaryLogicTypeIds[i];
                    if (primaryLogicTypeId) {
                        let primaryPropertyClass = app.findClass(primaryLogicTypeId, true, true);
                        if (primaryPropertyClass.kind.supports('association')) {
                            superCodingType.addGenericType(app.getPrimaryLogicTypeIds(primaryPropertyClass)[0]);
                        } else {
                            superCodingType.addGenericType(primaryPropertyClass);
                        }
                    } else { // 泛型主键类型
                        let genericParameter = i === 0 ? 'L' : 'R';
                        superCodingType.addGenericType(new CodingType('', genericParameter));
                        codingClass.addGenericType(new CodingType('', `${genericParameter} extends Serializable`));
                        codingClass.addImportClass('java.io.Serializable');
                    }
                }
            }
            // 普通实体无需添加主键泛型
        }
        return superCodingType;
    },
    generateRepoImplCode(app, clazz, options = {}) {
        let codingClass = this.createRepoCodingClass(app, clazz, options, 'repoImpl');
        if (clazz.abstractClass) {
            codingClass.modifier = 'abstract';
        } else {
            codingClass.annotations.push('Component');
            codingClass.addImportClass('org.springframework.stereotype.Component');
        }
        if (!codingClass.extends.length) {
            let superClassName;
            if (TypeKind.UNITY.equals(clazz.kind)) {
                superClassName = 'org.truenewx.tnxjee.repo.jpa.support.JpaUnityRepoxSupport';
            } else if (TypeKind.RELATION.equals(clazz.kind)) {
                superClassName = 'org.truenewx.tnxjee.repo.jpa.support.JpaRelationRepoxSupport';
            } else {
                superClassName = 'org.truenewx.tnxjee.repo.jpa.support.JpaRepoxSupport';
            }
            codingClass.extends = [this.createSuperCodingType(app, clazz, codingClass, superClassName)];
        }
        codingClass.implements =
            [new CodingType(codingClass.packageName, this.getRepoSimpleName(clazz, 'repox'))];

        if (options.queryable) {
            let queryMethod = this.addRepoQueryMethod(clazz, codingClass);
            queryMethod.annotations.push('Override');
            const getRepoQueryMethodBody = (jdkVersion = '17') => {

                const getQueryPropertyCondition = jdkVersion >= '17' ?
                    function (propertyTypeId, entityPropertyName, queryingPropertyName, fuzzy = false) {
                        let comparison = fuzzy ? ' like ' : '=';
                        return `            <#if ${queryingPropertyName}??>\n` +
                            `                and ${entityPropertyName}${comparison}:${queryingPropertyName}\n` +
                            '            </#if>\n';
                    } : function (propertyTypeId, entityPropertyName, queryingPropertyName, fuzzy = false) {
                        let condition = '';
                        let queryingPropertyGetterMethodName = 'get' + queryingPropertyName.firstToUpperCase();
                        let comparison = fuzzy ? ' like ' : '=';
                        if (propertyTypeId === STRING_CLASS_NAME) {
                            condition +=
                                `    if (StringUtils.isNotBlank(querying.${queryingPropertyGetterMethodName}())) {\n`;
                            condition +=
                                `        ql += " and ${entityPropertyName}${comparison}:${queryingPropertyName}";\n`;
                            condition += '    }\n';
                            codingClass.addImportClass('org.apache.commons.lang3.StringUtils');
                        } else {
                            condition += `    if (querying.${queryingPropertyGetterMethodName}() != null) {\n`;
                            condition +=
                                `        ql += " and ${entityPropertyName}${comparison}:${queryingPropertyName}";\n`;
                            condition += '    }\n';
                        }
                        return condition;
                    }

                // 获取参与关键字搜索的属性名称
                const getKeywordInvolvedPropertyNames = function () {
                    let propertyNames = [];
                    for (let property of clazz.properties) {
                        if (property.embedded?.length) {
                            let valueClass = app.findClass(property.typeId, true, true);
                            for (let embeddedMapping of property.embedded) {
                                if (clazz.keywordMappingIds.includes(embeddedMapping.id)) {
                                    let valueProperty = valueClass.getPropertyById(
                                        embeddedMapping.logicPropertyId);
                                    if (valueProperty) {
                                        propertyNames.push(property.name + '.' + valueProperty.name);
                                    }
                                }
                            }
                        } else if (clazz.keywordMappingIds.includes(property.mapping?.id)) {
                            propertyNames.push(property.name);
                        }
                    }
                    return propertyNames;
                }

                let keywordPropertyNames = getKeywordInvolvedPropertyNames();
                let body = '';
                if (jdkVersion >= '17') {
                    body += '    String ql = """\n';
                    body += `            from ${clazz.simpleName} where 1=1\n`;
                } else {
                    body += `    String ql = "from ${clazz.simpleName} where 1=1";\n`;
                }
                let fuzzyParamNames = [];
                if (keywordPropertyNames.length) {
                    fuzzyParamNames.push(KEYWORD_NAME);
                }
                for (let property of clazz.properties) {
                    // 忽略派生属性
                    if (property.derived) {
                        continue;
                    }
                    let entityPropertyName = property.name;
                    let propertyClass = app.findClass(property.typeId, true, true);
                    if (propertyClass instanceof ValueClass) { // 内嵌值模型引用
                        for (let embeddedMapping of property.embedded) {
                            let querying = embeddedMapping.querying;
                            if (querying) {
                                let valueProperty = propertyClass.getPropertyById(
                                    embeddedMapping.logicPropertyId);
                                if (valueProperty && !valueProperty.derived) { // 忽略派生属性
                                    let valuePropertyClass = app.findClass(valueProperty.typeId, true, true);
                                    let paramName = querying.name || this.getDefaultEmbeddedModelPropertyName(
                                        app, property.name, valueProperty.name, valuePropertyClass);
                                    let valuePropertyName = entityPropertyName + '.' + valueProperty.name;
                                    if (valuePropertyClass.kind.supports('association')) {
                                        valuePropertyName +=
                                            '.' + app.getPrimaryLogicNames(valuePropertyClass)[0];
                                    }
                                    body += getQueryPropertyCondition(valueProperty.typeId, valuePropertyName,
                                        paramName, querying.fuzzy);
                                    if (querying.fuzzy) {
                                        fuzzyParamNames.push(paramName);
                                    }
                                }
                            }
                        }
                    } else { // 普通属性
                        let querying = property.mapping?.querying;
                        if (querying) {
                            let paramName = querying.name || this.getDefaultModelPropertyName(app,
                                entityPropertyName, propertyClass);
                            if (propertyClass.kind.supports('association')) {
                                entityPropertyName += '.' + app.getPrimaryLogicNames(propertyClass)[0];
                            }
                            body += getQueryPropertyCondition(property.typeId, entityPropertyName, paramName,
                                querying.fuzzy);
                            if (querying.fuzzy) {
                                fuzzyParamNames.push(paramName);
                            }
                        }
                    }
                }

                if (keywordPropertyNames.length) {
                    if (jdkVersion >= '17') {
                        body += `            <#if ${KEYWORD_NAME}??>\n`;
                        body += '                and (\n';
                        for (let i = 0; i < keywordPropertyNames.length; i++) {
                            body += '                    ';
                            if (i > 0) {
                                body += 'or ';
                            }
                            body += `${keywordPropertyNames[i]} like :${KEYWORD_NAME}\n`;
                        }
                        body += '                )\n';
                        body += '            </#if>\n';
                    } else {
                        let keywordGetterMethodName = 'get' + KEYWORD_NAME.firstToUpperCase();
                        body += `    if (StringUtils.isNotBlank(querying.${keywordGetterMethodName}())) {\n`;
                        body += `        ql += " and (${keywordPropertyNames.map(
                            name => `${name} like :${KEYWORD_NAME}`).join(' or ')})";\n`;
                        body += '    }\n';
                    }
                    codingClass.addImportClass('org.apache.commons.lang3.StringUtils');
                    codingClass.addImportClass('org.truenewx.tnxjee.core.Strings');
                }

                if (jdkVersion >= '17') {
                    body += '            """;\n';
                }

                let excludedParamNameString = '';
                for (let fuzzyParamName of fuzzyParamNames) {
                    excludedParamNameString += ', "' + fuzzyParamName + '"';
                }
                body += `    Map<String, Object> params = paramMap(querying${excludedParamNameString});\n`;
                codingClass.addImportClass('java.util.Map');
                for (let fuzzyParamName of fuzzyParamNames) {
                    let paramGetterMethodName = 'get' + fuzzyParamName.firstToUpperCase();
                    body += `    if (StringUtils.isNotBlank(querying.${paramGetterMethodName}())) {\n` +
                        `        params.put("${fuzzyParamName}", Strings.PERCENT + querying.${paramGetterMethodName}() + Strings.PERCENT);\n` +
                        '    }\n';
                    codingClass.addImportClass('org.apache.commons.lang3.StringUtils');
                    codingClass.addImportClass('org.truenewx.tnxjee.core.Strings');
                }
                body += '    return query(ql, params, querying);';
                return body;
            }

            queryMethod.body = getRepoQueryMethodBody(options.jdkVersion);
        }

        return codingClass.toString();
    },
    getServicePackageName(entityClass) {
        return this.getLayerPackageName(entityClass.packageName, 'service');
    },
    getCommandPackageName(entityClass) {
        return this.getServicePackageName(entityClass) + '.model';
    },
    getCommandSimpleName(entityClass) {
        return entityClass.simpleName + 'Command';
    },
    generateCommandCode(app, clazz, options = {}) {
        let packageName = this.getCommandPackageName(clazz);
        let simpleName = this.getCommandSimpleName(clazz);
        let codingClass = new CodingClass(packageName, simpleName);
        codingClass.lombok = options.lombok || false;
        if (clazz.abstractClass) {
            codingClass.modifier = 'abstract';
        }
        if (options.extended) {
            let superClass = app.findClass(clazz.superClassId, true, true);
            if (superClass) {
                let superCodingType = new CodingType(this.getCommandPackageName(superClass),
                    this.getCommandSimpleName(superClass));
                codingClass.extends = [superCodingType];
            }
        }
        if (!codingClass.extends.length) {
            let commandInterface = new CodingType('org.truenewx.tnxjee.model.CommandModel');
            commandInterface.addGenericType(clazz);
            codingClass.implements = [commandInterface];
        }

        this.addModelCodingProperties(app, clazz, options, codingClass, 'command');

        return codingClass.toString();
    },
    getServiceSimpleName(entityClass, serviceType = 'service') {
        let simpleName;
        if (serviceType === 'serviceImpl' && entityClass.abstractClass) {
            simpleName = entityClass.simpleName + this.getEntityCodeTypeItem('service').caption + 'Support';
        } else {
            simpleName = entityClass.simpleName + this.getEntityCodeTypeItem(serviceType).caption;
        }
        return simpleName;
    },
    generateServiceInterfaceCode(app, clazz, options) {
        let codingClass = this.createServiceCodingClass(app, clazz, options, 'service');
        codingClass.kind = CodingClassKind.INTERFACE;
        if (!codingClass.extends.length) {
            // 属性映射中设置了命令模型，则需使用命令模型服务接口
            let usingCommand = clazz.properties.some(p => p.mapping?.command);
            let superInterfaceName;
            if (TypeKind.UNITY.equals(clazz.kind)) {
                if (usingCommand) {
                    superInterfaceName = 'org.truenewx.tnxjee.service.unity.CommandUnityService';
                } else {
                    superInterfaceName = 'org.truenewx.tnxjee.service.unity.SimpleUnityService';
                }
            } else if (TypeKind.RELATION.equals(clazz.kind)) {
                if (usingCommand) {
                    superInterfaceName = 'org.truenewx.tnxjee.service.relation.CommandRelationService';
                } else {
                    superInterfaceName = 'org.truenewx.tnxjee.service.relation.SimpleRelationService';
                }
            } else {
                superInterfaceName = 'org.truenewx.tnxjee.service.Service';
            }
            codingClass.extends = [this.createSuperCodingType(app, clazz, codingClass, superInterfaceName)];
        }

        if (options.queryable) {
            let queryCodingMethod = this.createQueryCodingMethod(clazz, codingClass);
            queryCodingMethod.body = null;
            codingClass.methods.push(queryCodingMethod);
        }

        return codingClass.toString();
    },
    createServiceCodingClass(app, clazz, options, serviceType) {
        let packageName = this.getServicePackageName(clazz);
        let simpleName = this.getServiceSimpleName(clazz, serviceType);
        let codingClass = new CodingClass(packageName, simpleName);
        if (options.extended) {
            let superClass = app.findClass(clazz.superClassId, true, true);
            if (superClass) {
                let superCodingType = new CodingType(this.getServicePackageName(superClass),
                    this.getServiceSimpleName(superClass, serviceType));
                // 父类如果为抽象类，则大概率需指定泛型为当前实体类
                if (superClass.abstractClass) {
                    superCodingType.addGenericType(new CodingType(clazz.packageName, clazz.simpleName));
                }
                codingClass.extends = [superCodingType];
            }
        }
        return codingClass;
    },
    createQueryCodingMethod(clazz, codingClass) {
        let codingMethod = new CodingMethod('query');
        codingMethod.returnType = new CodingType('org.truenewx.tnxjee.model.query.QueryResult');
        codingMethod.returnType.addGenericType(new CodingType(clazz.packageName, clazz.simpleName));
        let queryingPackageName = this.getQueryingPackageName(clazz);
        let queryingSimpleName = this.getQueryingSimpleName(clazz);
        let queryingCodingType = new CodingType(queryingPackageName, queryingSimpleName);
        codingMethod.addParameter(queryingCodingType, 'querying');
        codingClass.addImportClass(codingMethod.returnType, queryingCodingType);
        return codingMethod;
    },
    generateServiceImplCode(app, clazz, options) {
        let codingClass = this.createServiceCodingClass(app, clazz, options, 'serviceImpl');
        if (clazz.abstractClass) {
            codingClass.modifier = 'abstract';
        } else {
            codingClass.annotations.push('Service');
            codingClass.addImportClass('org.springframework.stereotype.Service');
        }
        if (!codingClass.extends.length) {
            let superClassName;
            if (TypeKind.UNITY.equals(clazz.kind)) {
                superClassName = 'org.truenewx.tnxjee.service.impl.unity.AbstractUnityService';
            } else if (TypeKind.RELATION.equals(clazz.kind)) {
                superClassName = 'org.truenewx.tnxjee.service.impl.relation.AbstractRelationService';
            } else {
                superClassName = 'org.truenewx.tnxjee.service.impl.AbstractService';
            }
            codingClass.extends = [this.createSuperCodingType(app, clazz, codingClass, superClassName)];
        }
        codingClass.implements = [new CodingType(codingClass.packageName, this.getServiceSimpleName(clazz))];

        let repoPackageName = this.getRepoPackageName(clazz);
        let repoSimpleName = this.getRepoSimpleName(clazz);
        let repoCodingType = new CodingType(repoPackageName, repoSimpleName);
        let repoCodingProperty = new CodingProperty(repoCodingType, repoSimpleName.firstToLowerCase());
        repoCodingProperty.annotations = ['Autowired'];
        codingClass.properties.push(repoCodingProperty);
        codingClass.addImportClass('org.springframework.beans.factory.annotation.Autowired');

        if (options.queryable) {
            let queryCodingMethod = this.createQueryCodingMethod(clazz, codingClass);
            queryCodingMethod.annotations = ['Override'];
            queryCodingMethod.body = `return this.${repoCodingProperty.name}.query(querying);`;
            codingClass.methods.push(queryCodingMethod);
        }

        if (TypeKind.UNITY.equals(clazz.kind) || TypeKind.RELATION.equals(clazz.kind)) {
            let primaryPropertyNames = app.getPrimaryLogicNames(clazz);
            let primaryCodingTypes = app.getPrimaryLogicTypeIds(clazz, true).map((typeId, i) => {
                let associationClass = this.getPropertyAssociationClass(app, {typeId});
                if (associationClass) {
                    primaryPropertyNames[i] += app.getPrimaryLogicNames(associationClass)[0].firstToUpperCase();
                    return new CodingType(app.getPrimaryLogicTypeIds(associationClass)[0]);
                } else {
                    return new CodingType(typeId).toWrapper();
                }
            });

            let entityClassCodingType = new CodingType(clazz.packageName, clazz.simpleName);

            // 抽象实体生成创建实体的方法
            let createCodingMethodName = null;
            if (clazz.abstractClass) {
                createCodingMethodName = 'create';
                let createCodingMethod = new CodingMethod(createCodingMethodName);
                createCodingMethod.modifier = 'protected';
                createCodingMethod.returnType = entityClassCodingType;
                createCodingMethod.body = 'return ClassUtil.newInstance(getEntityClass());';
                codingClass.addImportClass('org.truenewx.tnxjee.core.util.ClassUtil');
                codingClass.methods.push(createCodingMethod);
            }

            // 生成beforeSave方法
            let beforeSaveCodingMethod = new CodingMethod('beforeSave');
            beforeSaveCodingMethod.annotations = ['Override'];
            beforeSaveCodingMethod.modifier = 'protected';
            beforeSaveCodingMethod.returnType = entityClassCodingType;
            if (TypeKind.UNITY.equals(clazz.kind)) {
                let primaryCodingType = primaryCodingTypes[0];
                if (!primaryCodingType) {
                    primaryCodingType = new CodingType('', 'K');
                    primaryCodingTypes[0] = primaryCodingType;
                }
                beforeSaveCodingMethod.addParameter(primaryCodingType, 'id');
            } else {
                beforeSaveCodingMethod.addParameter(primaryCodingTypes[0], primaryPropertyNames[0]);
                beforeSaveCodingMethod.addParameter(primaryCodingTypes[1], primaryPropertyNames[1]);
            }
            let usingCommand = clazz.properties.some(p => p.mapping?.command);
            if (usingCommand) {
                let commandModelCodingType = new CodingType('org.truenewx.tnxjee.model.CommandModel');
                commandModelCodingType.addGenericType(entityClassCodingType);
                beforeSaveCodingMethod.addParameter(commandModelCodingType, 'commandModel');
            } else {
                beforeSaveCodingMethod.addParameter(entityClassCodingType, 'command');
            }

            const getCommandToEntityCode = indent => {
                let prefix = ' '.repeat(indent);
                let s = '';
                let entityVariableName = clazz.kind.name.toLowerCase();
                if (TypeKind.UNITY.equals(clazz.kind)) {
                    s += prefix + `${clazz.simpleName} ${entityVariableName};\n`;
                    s += prefix + `if (id == null) {\n`;
                    if (createCodingMethodName) {
                        s += prefix + `    ${entityVariableName} = ${createCodingMethodName}();\n`;
                    } else {
                        s += prefix + `    ${entityVariableName} = new ${clazz.simpleName}();\n`;
                    }
                    s += prefix + `} else {\n`;
                    s += prefix + `    ${entityVariableName} = load(id);\n`;
                    s += prefix + `}\n`;
                } else {
                    s += prefix + `${clazz.simpleName} ${entityVariableName};\n`;
                    s +=
                        prefix + `if (${primaryPropertyNames[0]} == null || ${primaryPropertyNames[1]} == null) {\n`;
                    if (createCodingMethodName) {
                        s += prefix + `    ${entityVariableName} = ${createCodingMethodName}();\n`;
                    } else {
                        s += prefix + `    ${entityVariableName} = new ${clazz.simpleName}();\n`;
                    }
                    s += prefix + `} else {\n`;
                    s +=
                        prefix + `    ${entityVariableName} = load(${primaryPropertyNames[0]}, ${primaryPropertyNames[1]});\n`;
                    s += prefix + `}\n`;
                }
                s += prefix + `// TODO 请确认 command -> ${entityVariableName}\n`;

                for (let property of clazz.properties) {
                    let command = property.mapping?.command;
                    if (command) {
                        let getter = null;
                        let propertyClass = app.findClass(property.typeId, true, true);
                        let propertyName = command.name || this.getDefaultModelPropertyName(app, property.name,
                            propertyClass);
                        if (propertyClass && propertyClass.kind.supports('association')) {
                            if (!command.typeId || new CodingType(command.typeId).toWrapper()
                                .getName() === propertyClass.getPrimaryLogicTypeIds()[0].getName()) {
                                let refServicePackageName = this.getServicePackageName(propertyClass);
                                let refServiceSimpleName = this.getServiceSimpleName(propertyClass);
                                let serviceRefPropertyName = refServiceSimpleName.firstToLowerCase();
                                getter =
                                    `this.${serviceRefPropertyName}.load(command.get${propertyName.firstToUpperCase()}())`;
                                let refServiceCodingType = new CodingType(refServicePackageName,
                                    refServiceSimpleName);
                                let serviceRefProperty = new CodingProperty(refServiceCodingType,
                                    serviceRefPropertyName);
                                serviceRefProperty.annotations = ['AutoWired'];
                                codingClass.properties.push(serviceRefProperty);
                                codingClass.addImportClass(refServiceCodingType);
                            }
                        } else if (!command.typeId || command.typeId === property.typeId) {
                            getter = `command.get${propertyName.firstToUpperCase()}()`;
                        }
                        if (getter) {
                            s += prefix + `${entityVariableName}.set${property.name.firstToUpperCase()}(${getter});\n`;
                        }
                    }
                }

                s += prefix + `return ${entityVariableName};\n`;
                return s;
            }

            let commandPackageName = this.getCommandPackageName(clazz);
            let commandSimpleName = this.getCommandSimpleName(clazz);
            codingClass.addImportClass(new CodingType(commandPackageName, commandSimpleName));
            let body;
            if (usingCommand) {
                if (options.jdkVersion >= '17') {
                    body = `    if (commandModel instanceof ${commandSimpleName} command) {\n`;
                } else {
                    body = `    if (commandModel instanceof ${commandSimpleName}) {\n`;
                    body += `        ${commandSimpleName} command = (${commandSimpleName}) commandModel;\n\n`;
                }
                body += getCommandToEntityCode(8);
                body += '    }\n';
                body += '    return null;\n';
            } else {
                body = getCommandToEntityCode(4);
            }
            beforeSaveCodingMethod.body = body;
            codingClass.methods.push(beforeSaveCodingMethod);

            // 生成beforeDelete方法
            let beforeDeleteCodingMethod = new CodingMethod('beforeDelete');
            beforeDeleteCodingMethod.annotations = ['Override'];
            beforeDeleteCodingMethod.modifier = 'protected';
            beforeDeleteCodingMethod.returnType = entityClassCodingType;
            if (TypeKind.UNITY.equals(clazz.kind)) {
                beforeDeleteCodingMethod.addParameter(primaryCodingTypes[0], 'id');
            } else {
                beforeDeleteCodingMethod.addParameter(primaryCodingTypes[0], primaryPropertyNames[0]);
                beforeDeleteCodingMethod.addParameter(primaryCodingTypes[1], primaryPropertyNames[1]);
            }
            body = '    // TODO 默认允许随意删除，请确认\n';
            body += '    return null;\n';
            beforeDeleteCodingMethod.body = body;
            codingClass.methods.push(beforeDeleteCodingMethod);
        }

        return codingClass.toString();
    },
    getControllerPackageName(entityClass) {
        return this.getLayerPackageName(entityClass.packageName, 'web') + '.controller';
    },
    getControllerSimpleName(entityClass) {
        let simpleName = entityClass.simpleName + 'Controller';
        if (entityClass.abstractClass) {
            simpleName = simpleName + 'Support';
        }
        return simpleName;
    },
    createControllerCodingClass(app, clazz, options) {
        let packageName = this.getControllerPackageName(clazz);
        let simpleName = this.getControllerSimpleName(clazz);
        let codingClass = new CodingClass(packageName, simpleName);
        if (clazz.abstractClass) {
            codingClass.modifier = 'abstract';
        }
        if (options.extended) {
            let superClass = app.findClass(clazz.superClassId, true, true);
            if (superClass) {
                let superCodingType = new CodingType(this.getControllerPackageName(superClass),
                    this.getControllerSimpleName(superClass));
                // 父类如果为抽象类，则大概率需指定泛型为当前实体类
                if (superClass.abstractClass) {
                    superCodingType.addGenericType(new CodingType(clazz.packageName, clazz.simpleName));
                }
                codingClass.extends = [superCodingType];
            }
        }
        return codingClass;
    },
    generateControllerCode(app, clazz, options) {
        if (TypeKind.RELATION.equals(clazz.kind)) {
            return '';
        }
        let codingClass = this.createControllerCodingClass(app, clazz, options);
        if (clazz.abstractClass) {
            codingClass.modifier = 'abstract';
        } else {
            const toMappingPathSegment = function (name) {
                return name.splitByUpperCaseLetter().join('-').toLowerCase();
            }

            let mappingPath = '/' + toMappingPathSegment(clazz.simpleName);
            let moduleName = BaseType.getModuleName(clazz.packageName);
            if (moduleName) {
                mappingPath = '/' + toMappingPathSegment(moduleName) + mappingPath;
            }
            codingClass.annotations = ['RestController', `RequestMapping("${mappingPath}")`];
            let entityCodingType = new CodingType(clazz.packageName, clazz.simpleName);
            codingClass.addImportClass('org.springframework.web.bind.annotation.RestController',
                'org.springframework.web.bind.annotation.RequestMapping',
                'org.springframework.web.bind.annotation.GetMapping',
                'org.springframework.web.bind.annotation.PostMapping',
                'org.springframework.web.bind.annotation.RequestBody',
                'org.springframework.web.bind.annotation.PathVariable',
                'org.truenewx.tnxjee.webmvc.security.config.annotation.ConfigAuthority',
                entityCodingType);

            let servicePackageName = this.getServicePackageName(clazz);
            let serviceSimpleName = this.getServiceSimpleName(clazz);
            let serviceCodingType = new CodingType(servicePackageName, serviceSimpleName);
            let servicePropertyName = serviceSimpleName.firstToLowerCase();
            let serviceCodingProperty = new CodingProperty(serviceCodingType, servicePropertyName);
            serviceCodingProperty.annotations = ['Autowired'];
            codingClass.properties.push(serviceCodingProperty);
            codingClass.addImportClass('org.springframework.beans.factory.annotation.Autowired');

            // 分页查询方法
            if (options.queryable) {
                let queryCodingMethod = this.createQueryCodingMethod(clazz, codingClass);
                queryCodingMethod.annotations = [`GetMapping("/${toMappingPathSegment(queryCodingMethod.name)}")`,
                    'ConfigAuthority', 'ResultFilter()'];
                codingClass.addImportClass('org.truenewx.tnxjee.webmvc.http.annotation.ResultFilter');
                queryCodingMethod.body = `return this.${servicePropertyName}.query(querying);`;
                codingClass.methods.push(queryCodingMethod);
            }

            let usingCommand = clazz.properties.some(p => p.mapping?.command);
            let commandCodingMethodParameter;
            if (usingCommand) {
                let commandPackageName = this.getCommandPackageName(clazz);
                let commandSimpleName = this.getCommandSimpleName(clazz);
                let commandCodingType = new CodingType(commandPackageName, commandSimpleName);
                commandCodingMethodParameter = new CodingMethodParameter(commandCodingType, 'command');
                codingClass.addImportClass(commandCodingType);
            } else {
                commandCodingMethodParameter = new CodingMethodParameter(entityCodingType, 'command');
            }
            commandCodingMethodParameter.annotation = 'RequestBody';

            // 添加方法
            let addCodingMethod = new CodingMethod('add');
            addCodingMethod.annotations = ['PostMapping("/add")', 'ConfigAuthority'];
            addCodingMethod.parameters = [commandCodingMethodParameter];
            addCodingMethod.body = `this.${servicePropertyName}.add(command);`;
            codingClass.methods.push(addCodingMethod);

            let primaryPropertyTypeId = app.getPrimaryLogicTypeIds(clazz, true)[0];
            let primaryPropertyName = app.getPrimaryLogicNames(clazz)[0];

            // 修改获取方法
            let updateGetCodingMethod = new CodingMethod('update');
            updateGetCodingMethod.annotations = [`GetMapping("/{${primaryPropertyName}}/update")`, 'ConfigAuthority'];
            let primaryParameter = new CodingMethodParameter(new CodingType(primaryPropertyTypeId),
                primaryPropertyName);
            primaryParameter.annotation = `PathVariable("${primaryPropertyName}")`;
            updateGetCodingMethod.returnType = commandCodingMethodParameter.type;
            updateGetCodingMethod.parameters = [primaryParameter];
            let entityVariableName = clazz.kind.name.toLowerCase();
            let updateGetBody = `${clazz.simpleName} ${entityVariableName} = this.${servicePropertyName}.load(${primaryPropertyName});\n` +
                `    ${commandCodingMethodParameter.type} command = new ${commandCodingMethodParameter.type}();\n` +
                `    // TODO 请确认 ${entityVariableName} -> command\n`;
            for (let property of clazz.properties) {
                let command = property.mapping?.command;
                if (command) {
                    let getter = null;
                    let propertyClass = app.findClass(property.typeId, true, true);
                    let propertyName = command.name || this.getDefaultModelPropertyName(app, property.name,
                        propertyClass);
                    if (propertyClass && propertyClass.kind.supports('association')) {
                        if (!command.typeId || new CodingType(command.typeId).toWrapper()
                            .getName() === propertyClass.getPrimaryLogicTypeIds()[0].getName()) {
                            let refServicePackageName = this.getServicePackageName(propertyClass);
                            let refServiceSimpleName = this.getServiceSimpleName(propertyClass);
                            let serviceRefPropertyName = refServiceSimpleName.firstToLowerCase();
                            getter =
                                `this.${serviceRefPropertyName}.load(${entityVariableName}.get${propertyName.firstToUpperCase()}())`;
                            let refServiceCodingType = new CodingType(refServicePackageName,
                                refServiceSimpleName);
                            let serviceRefProperty = new CodingProperty(refServiceCodingType,
                                serviceRefPropertyName);
                            serviceRefProperty.annotations = ['AutoWired'];
                            codingClass.properties.push(serviceRefProperty);
                            codingClass.addImportClass(refServiceCodingType);
                        }
                    } else if (!command.typeId || command.typeId === property.typeId) {
                        getter = `${entityVariableName}.get${propertyName.firstToUpperCase()}()`;
                    }
                    if (getter) {
                        updateGetBody += `    command.set${property.name.firstToUpperCase()}(${getter});\n`;
                    }
                }
            }
            updateGetBody += `    return ${usingCommand ? 'command' : entityVariableName};`;
            updateGetCodingMethod.body = updateGetBody;
            codingClass.methods.push(updateGetCodingMethod);

            // 修改提交方法
            let updatePostCodingMethod = new CodingMethod('update');
            updatePostCodingMethod.annotations = [`PostMapping("/{${primaryPropertyName}}/update")`, 'ConfigAuthority'];
            updatePostCodingMethod.parameters = [primaryParameter, commandCodingMethodParameter];
            updatePostCodingMethod.body = `this.${servicePropertyName}.update(${primaryPropertyName}, command);`;
            codingClass.methods.push(updatePostCodingMethod);

            // 删除方法
            let deleteCodingMethod = new CodingMethod('delete');
            deleteCodingMethod.annotations = [`PostMapping("/{${primaryPropertyName}}/delete")`, 'ConfigAuthority'];
            deleteCodingMethod.parameters = [primaryParameter];
            deleteCodingMethod.body = `this.${servicePropertyName}.delete(${primaryPropertyName});`;
            codingClass.methods.push(deleteCodingMethod);
        }

        return codingClass.toString();
    },
    save(path, code, codeType) {
        const doSave = function () {
            tnx.util.file.write(path, code).then(() => {
                tnx.toast('已保存');
            }).catch(error => {
                tnx.error(error);
            });
        }
        if (code) {
            if (codeType) {
                tnx.util.file.exists(path).then(exists => {
                    if (exists) {
                        let codeTypeItem = this.getEntityCodeTypeItem(codeType);
                        let caption = codeTypeItem?.caption || '';
                        tnx.confirm(`${caption}代码文件已存在，确定要覆盖吗？`).then(yes => {
                            if (yes) {
                                doSave();
                            }
                        });
                    } else {
                        doSave();
                    }
                });
            } else {
                doSave();
            }
        }
    },
    copy(code) {
        if (code) {
            tnx.util.clipboard.write(code).then(() => {
                tnx.toast('已复制');
            });
        }
    },
}
